/*
 * PhraseFileManager.cpp
 *
 *  Created on: Dec 8, 2012
 *      Author: bruno
 */

#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <cstring>

#include "FileNamesConstants.h"
#include "../physical_layer/file_system/PhraseDictionary.h"
#include "../physical_layer/file_system/Phrase.h"

#define ATTR_DELIM '|'

using physical::PhraseDictionary;
using physical::Phrase;
using std::cerr;
using std::endl;

string registro;
string loadFileName;
string outputFileName;
string stopWordsFile;

typedef struct
{
	size_t id;
	std::string phrase;
	std::string surname;
	std::string name;

	bool parseData( std::string &line )
	{
		char c;
		std::stringstream ss(line);

		if ( !( ss >> id && ss >> c && c == ATTR_DELIM ) )
		{
			return false;
		}

		if ( !getline( ss, phrase, ATTR_DELIM ) )
		{
			return false;
		}

		if ( !getline( ss, surname, ATTR_DELIM ) )
		{
			return false;
		}

		if ( !getline( ss, name, ATTR_DELIM ) )
		{
			name.clear();
		}

		return true;
	}

	bool parseDataNoID( std::string &line )
	{
		std::stringstream ss( line );

		if ( !getline( ss, name, ATTR_DELIM ) )
			return false;

		if ( !getline( ss, surname, ATTR_DELIM ) )
			return false;

		if ( !getline( ss, phrase, ATTR_DELIM ) )
			name.clear();

		return true;
	}

} PhraseBuffer_t;

/**
 *  Esta clase es un diccionario de stop-words
 *  Se utiliza para filtrar stop-words
 */
class StopWordsDictionary
{
	std::list<std::string> stopWords;

public:
	void load( std::ifstream &ifs )
	{
		std::string lineBuffer;

		while ( getline( ifs, lineBuffer ) )
		{
			stopWords.push_back( lineBuffer );
		}
	}

	bool isStopWord(std::string &str)
	{
		for ( auto it = stopWords.begin(); it != stopWords.end(); it++)
		{
			if (it->compare(str) == 0)
			{
				return true;
			}
		}

		return false;
	}

};

/**
 * Estructura invalid symbol
 * Representa un caracter inválido (tildes y demás)
 *  y tiene un posible reemplazo para volverlo valido
 */
struct invalidSymbol
{
	std::string m_symbol;
	std::string m_replacement;

	invalidSymbol( std::string const &symbol, std::string const &replacement ) :
			m_symbol(symbol), m_replacement(replacement)
	{
	}
};

class InvalidSymbolsDictionary
{
private:
	typedef std::list<invalidSymbol> invalidSymbolsCollection;

public:
	typedef invalidSymbolsCollection::iterator iterator;

public:
	InvalidSymbolsDictionary()
    {
		initialize();
	}

	iterator begin()
	{
		return invalidSymbols.begin();
	}

	iterator end()
	{
		return invalidSymbols.end();
	}

private:
	invalidSymbolsCollection invalidSymbols;

	void initialize()
	{
		invalidSymbols.push_back( invalidSymbol( "á", "a" ) );
		invalidSymbols.push_back( invalidSymbol( "à", "a" ) );
		invalidSymbols.push_back( invalidSymbol( "â", "a" ) );
		invalidSymbols.push_back( invalidSymbol( "ä", "a" ) );

		invalidSymbols.push_back( invalidSymbol( "é", "e" ) );
		invalidSymbols.push_back( invalidSymbol( "è", "e" ) );
		invalidSymbols.push_back( invalidSymbol( "ê", "e" ) );
		invalidSymbols.push_back( invalidSymbol( "ë", "e" ) );

		invalidSymbols.push_back( invalidSymbol( "í", "i" ) );
		invalidSymbols.push_back( invalidSymbol( "ì", "i" ) );
		invalidSymbols.push_back( invalidSymbol( "î", "i" ) );
		invalidSymbols.push_back( invalidSymbol( "ï", "i" ) );

		invalidSymbols.push_back( invalidSymbol( "ó", "o" ) );
		invalidSymbols.push_back( invalidSymbol( "ò", "o" ) );
		invalidSymbols.push_back( invalidSymbol( "ô", "o" ) );
		invalidSymbols.push_back( invalidSymbol( "ö", "o" ) );

		invalidSymbols.push_back( invalidSymbol( "ú", "u" ) );
		invalidSymbols.push_back( invalidSymbol( "ù", "u" ) );
		invalidSymbols.push_back( invalidSymbol( "û", "u" ) );
		invalidSymbols.push_back( invalidSymbol( "ü", "u" ) );

		invalidSymbols.push_back( invalidSymbol( ",", " " ) );
		invalidSymbols.push_back( invalidSymbol( ".", " " ) );
		invalidSymbols.push_back( invalidSymbol( ";", " " ) );
		invalidSymbols.push_back( invalidSymbol( ":", " " ) );
		invalidSymbols.push_back( invalidSymbol( "?", " " ) );
		invalidSymbols.push_back( invalidSymbol( "|", " " ) );
		invalidSymbols.push_back( invalidSymbol( "¿", " " ) );
		invalidSymbols.push_back( invalidSymbol( "!", " " ) );
		invalidSymbols.push_back( invalidSymbol( "¡", " " ) );
		invalidSymbols.push_back( invalidSymbol( "[", " " ) );
		invalidSymbols.push_back( invalidSymbol( "]", " " ) );
		invalidSymbols.push_back( invalidSymbol( "(", " " ) );
		invalidSymbols.push_back( invalidSymbol( ")", " " ) );
		invalidSymbols.push_back( invalidSymbol( "<", " " ) );
		invalidSymbols.push_back( invalidSymbol( ">", " " ) );
		invalidSymbols.push_back( invalidSymbol( "'", " " ) );
		invalidSymbols.push_back( invalidSymbol( "/", " " ) );
		invalidSymbols.push_back( invalidSymbol( "-", " " ) );
		invalidSymbols.push_back( invalidSymbol( "~", " " ) );
		invalidSymbols.push_back( invalidSymbol( "$", " " ) );
		invalidSymbols.push_back( invalidSymbol( "%", " " ) );
		invalidSymbols.push_back( invalidSymbol( "#", " " ) );
		invalidSymbols.push_back( invalidSymbol( "@", " " ) );
		invalidSymbols.push_back( invalidSymbol( "º", " " ) );
		invalidSymbols.push_back( invalidSymbol( "ª", " " ) );
		invalidSymbols.push_back( invalidSymbol( "*", " " ) );
		invalidSymbols.push_back( invalidSymbol( "+", " " ) );
		invalidSymbols.push_back( invalidSymbol( "^", " " ) );
		invalidSymbols.push_back( invalidSymbol( "¬", " " ) );
		invalidSymbols.push_back( invalidSymbol( "½", " " ) );
		invalidSymbols.push_back( invalidSymbol( "¨", " " ) );
		invalidSymbols.push_back( invalidSymbol( "…", " " ) );
		invalidSymbols.push_back( invalidSymbol( "\"", " " ) );
		invalidSymbols.push_back( invalidSymbol( "\\", " " ) );
	}
};

InvalidSymbolsDictionary invalidSymbolsDictionary;

/**
 * Función que reemplaza cada caracter considerado "simbolo extraño"
 *  por un "equivalente aceptable"
 *
 * @param str
 */
void replaceSymbols(std::string &str)
{
	auto it = invalidSymbolsDictionary.begin();

	for ( ; it != invalidSymbolsDictionary.end(); it++ )
	{
		size_t pos = str.find( it->m_symbol );
		while ( pos != std::string::npos )
		{
			str.replace( pos, it->m_symbol.size(), it->m_replacement );
			pos = str.find( it->m_symbol );
		}
	}
}

/**
 *  Función personalizada que pasa un string a minúsculas
 *  Nota: es personalizada porque tolower parece que rompia las ñ
 *
 * @param str
 */
void toLower( std::string &str )
{
	std::string upper_enie( "Ñ" );
	std::string lower_enie( "ñ" );

	// reemplazo las Ñ por ñ
	size_t pos = str.find( upper_enie );

	while ( pos != std::string::npos )
	{
		str.replace( pos, upper_enie.size(), lower_enie );
		pos = str.find( upper_enie );
	}

	// ahora aplico tolower a las demás letras
	for ( auto it = str.begin(); it != str.end(); it++ )
	{
		if ( *it <= 'Z' && *it >= 'A' )
			*it -= 'Z' - 'z';
	}
}

/**
 * Función encargada de realizar la normalización de palabras
 *
 * @param word
 * @return
 */
std::string normalize( std::string str )
{
	std::string normalizedStr( str );
	std::stringstream ss;

	// paso las letras de la palabra a minúsculas
	toLower( normalizedStr );

	// realizo reemplazos de tildes y otros símbolos
	replaceSymbols( normalizedStr );

	return normalizedStr;
}

void parseArgs( int argc, char* argv[] )
{

	for ( int i = 1; i < argc; ++i )
	{
		if ( strcmp(argv[i], "-l") == 0 )
		{
			loadFileName.assign( argv[i + 1] );
			stopWordsFile.assign( argv[i + 2] );
			i += 2;
			continue;
		}

		if ( strcmp(argv[i], "-o") == 0 )
		{
			outputFileName = argv[i + 1];
			i++;
			continue;
		}
	}
}

void invalid_args()
{
	std::cerr << "Argumentos inválidos. " << std::endl
			<< "Uso: app [-l <fileName> <stopWords> | -o <outputFileName> ]"
			<< std::endl;
	exit(1);
}


void loadFile( std::string &inputFile, PhraseDictionary &dictionary,
		StopWordsDictionary &stopWords )
{

	std::ifstream inFile;
	std::string lineBuffer;
	size_t lineNumber = 0;

	inFile.open( inputFile.c_str() );

	while ( getline( inFile, lineBuffer ) )
	{

		PhraseBuffer_t registerData;

		if ( lineBuffer.empty() )
		{
			continue;
		}

		if ( !registerData.parseDataNoID( lineBuffer ) )
		{
			std::cerr << "Error al parsear el archivo en la línea: "
					<< lineNumber << std::endl;
			exit(12);

		}
		else
		{
			dictionary.add( Phrase( registerData.phrase, registerData.surname, registerData.name ) );
		}

		lineNumber++;
	}

}

void loadStopWords( StopWordsDictionary &stopWordsDictionary )
{
	std::ifstream input( stopWordsFile );

	stopWordsDictionary.load( input );
}



int main( int argc, char **argv )
{
	if ( argc < 3 )
	{
		invalid_args();
	}

	parseArgs( argc, argv );

	StopWordsDictionary stopWordsDictionary;

	if ( !stopWordsFile.empty() )
	{
		loadStopWords(stopWordsDictionary);
	}

	if ( !loadFileName.empty() )
	{
		PhraseDictionary dictionary (phraseDictionaryFileName, std::ios_base::in | std::ios_base::out | std::ios_base::trunc);

		loadFile(loadFileName, dictionary, stopWordsDictionary);

	}

	if ( !outputFileName.empty() )
	{
		PhraseDictionary dictionary;

		dictionary.open( phraseDictionaryFileName, std::ios_base::in | std::ios_base::out);

		std::ofstream out ( outputFileName, std::ios_base::out | std::ios_base::trunc );

        PhraseDictionary::Record record = dictionary.getFirst();

		do
		{
		    Phrase phrase = record.get();
			out //<< record.getPosition() << ATTR_DELIM
                << phrase.getAuthorName() << ATTR_DELIM
                << phrase.getAuthorSurname() << ATTR_DELIM
                << phrase.getText() << endl;
		}
		while ( dictionary.hasNext(record) && (record = dictionary.getNext(record)) == record );


		out.close();
		dictionary.close();
	}

	return 0;
}

